using System;
using System.Collections.Generic;
using System.IO;

namespace SharpFastCGI.Internals
{
	/// <summary>
	/// FastCGI Records writer.
	/// </summary>
	/// <remarks>
	/// Each method call results in new records being sent completely.
	/// </remarks>
	internal class OutputBuffer
	{
		private System.Text.Encoding mEncoding = System.Text.Encoding.ASCII;
		private Stream mStream;
		private byte[] mBuffer;
		private int mMaxPayloadLength;
		
		public OutputBuffer(int BufferSize)
		{
			BufferSize += 8 - (BufferSize % 8);
			mBuffer = new byte[BufferSize];
			mMaxPayloadLength = BufferSize - 8;
		}
		
		public void Initialize(Stream Stream)
		{
			mStream = Stream;
		}
		
		public void Flush()
		{
			mStream.Flush();
		}
		
		private void WriteOnce(byte[] Buffer, int Offset, int Length)
		{
			while(Length > 0)
			{
				int written = mStream.Write(Buffer, Offset, Length);
				Offset += written;
				Length -= written;
			}
		}
		
		private void CopyHeader(RecordType Type, ushort RequestId, ushort ContentLength)
		{
			mBuffer[0] = 1;
			mBuffer[1] = Type;
			mBuffer[2] = (byte)(RequestId >> 8);
			mBuffer[3] = (byte)RequestId;
			mBuffer[4] = (byte)(ContentLength >> 8);
			mBuffer[5] = (byte)ContentLength;
			mBuffer[6] = 0;
			//mBuffer[7] = 0;
		}
		
		private void WriteStringLength(ref int Offset, int Length)
		{
			if(nameLen <= 127)
			{
				mBuffer[Offset++] = (byte)Length;
			}
			else
			{
				mBuffer[Offset] = (byte)(Length >> 24);
				mBuffer[Offset+1] = (byte)(Length >> 16);
				mBuffer[Offset+2] = (byte)(Length >> 8);
				mBuffer[Offset+3] = (byte)Length;
				Offset += 4;
			}
		}
		
		private void WriteNameValuePairsInSingleRecord(LinkedList<KeyValuePair<string,string>> PairsForCurrentRecord, int ContentLength)
		{
			CopyHeader(RecordType.GetValuesResult, 0, ContentLength);
			int offset = 8;
			
			foreach(KeyValuePair<string,string> pair in PairsForCurrentRecord)
			{
				string name = pair.Key, value = pair.Value;
				int nameLen, valueLen;
				int nameLeft = nameLen = mEncoding.GetByteCount(name);
				int valueLeft = valueLen = mEncoding.GetByteCount(value);
				
				if(mBuffer.Length - offset < 8)
				{
					WriteOnce(mBuffer, 0, offset);
					offset = 0;
				}
				
				WriteStringLength(ref offset, nameLen);
				WriteStringLength(ref offset, valueLen);
				
				for(;;)
				{
					int leftInBuffer = mBuffer.Length - offset;
					
					if(nameLeft < leftInBuffer)
					{
						offset += mEncoding.GetBytes(name, nameLen - nameLeft, nameLeft, mBuffer, offset);
						break;
					}
					else
					{
						offset += mEncoding.GetBytes(name, nameLen - nameLeft, leftInBuffer, mBuffer, offset);
						WriteOnce(mBuffer, 0, offset);
						offset = 0;
						
						nameLeft -= leftInBuffer;
					}
				}
				
				for(;;)
				{
					int leftInBuffer = mBuffer.Length - offset;
					
					if(valueLeft < leftInBuffer)
					{
						offset += mEncoding.GetBytes(value, valueLen - valueLeft, valueLeft, mBuffer, offset);
						break;
					}
					else
					{
						offset += mEncoding.GetBytes(value, valueLen - valueLeft, leftInBuffer, mBuffer, offset);
						WriteOnce(mBuffer, 0, offset);
						offset = 0;
						
						valueLeft -= leftInBuffer;
					}
				}
			}
		}
		
		public void Write_UnknownType(ushort RequestId, byte RecordType)
		{
			CopyHeader(RecordType.Unknown, RequestId, 8);
			mBuffer[8] = RecordType;
			//mBuffer[9] = mBuffer[10] = mBuffer[11] = mBuffer[12] = mBuffer[13] = mBuffer[14] = mBuffer[15] = 0;
			WriteOnce(mBuffer, 0, 16);
		}
		
		public void Write_EndRequest(ushort RequestId, int ApplicationStatus, ProtocolStatus ProtocolStatus)
		{
			CopyHeader(RecordType.EndRequest, RequestId, 8);
			mBuffer[8] = (byte)(ApplicationStatus >> 24);
			mBuffer[9] = (byte)(ApplicationStatus >> 16);
			mBuffer[10] = (byte)(ApplicationStatus >> 8);
			mBuffer[11] = (byte)ApplicationStatus;
			mBuffer[12] = ProtocolStatus;
			//mBuffer[13] = mBuffer[14] = mBuffer[15] = 0;
			WriteOnce(mBuffer, 0, 16);
		}
		
		public void Write_StdOutErr_Stream(ushort RequestId, RecordType Type, byte[] Buffer, int Offset, int Length)
		{
			do
			{
				int contentLength = Math.Min(Length, 65535);
				Length -= contentLength;
				int firstPacketPayloadLen = Math.Min(contentLength, mMaxPayloadLength);
				
				CopyHeader(Type, RequestId, contentLength);
				Array.Copy(Buffer, Offset, mBuffer, 8, firstPacketPayloadLen);
				WriteOnce(mBuffer, 0, 8 + firstPacketPayloadLen);
				contentLength -= firstPacketPayloadLen;
				Offset += firstPacketPayloadLen;
				
				if(contentLength > 0)
				{
					WriteOnce(Buffer, Offset, contentLength);
				}
			} while(Length > 0);
		}
		
		public void Write_StdOutErr_CloseStream(ushort RequestId, RecordType Type)
		{
			CopyHeader(Type, RequestId, 0);
			WriteOnce(mBuffer, 0, 8);
		}
		
		public void Write_GetValuesResult(IEnumerable<KeyValuePair<string,string>> NameValuePairs)
		{
			LinkedList<KeyValuePair<string,string>> pairsForCurrentRecord
				= new LinkedList<KeyValuePair<string,string>>();
			
			int contentSpaceLeft = 65535; // max length for a single record
			
			foreach(KeyValuePair<string,string> pair in NameValuePairs)
			{
				int nameLen = mEncoding.GetByteCount(pair.Key);
				int valueLen = mEncoding.GetByteCount(pair.Value);
				int encodedPairLen = nameLen + valueLen;
				encodedPairLen += nameLen <= 127 ? 1 : 4;
				encodedPairLen += valueLen <= 127 ? 1 : 4;
				
				if(encodedPairLen > contentSpaceLeft) // Not enough space left in the current record
				{
					WriteNameValuePairsInSingleRecord(pairsForCurrentRecord, 65535 - contentSpaceLeft);
					pairsForCurrentRecord.Clear();
					contentSpaceLeft = 65535;
				}
				
				contentSpaceLeft -= encodedPairLen;
				pairsForCurrentRecord.AddLast(pair);
			}
			
			WriteNameValuePairsInSingleRecord(pairsForCurrentRecord, 65535 - contentSpaceLeft);
		}
		
		/*public void Write_GetValuesResult_Stream(IEnumerable<KeyValuePair<string,string>> NameValuePairs)
		{
			int offset = 8;
			
			foreach(KeyValuePair<string,string> pair in NameValuePairs)
			{
				int left = mBuffer.Length - offset;
				
				if(left < 8)
				{
					WriteHeader(RecordType.GetValuesResult, 0, offset - 8);
					WriteOnce(mBuffer, 0, offset);
					offset = 8;
				}
				
				int nameLen = mEncoding.GetByteCount(Name);
				int valueLen = mEncoding.GetByteCount(Value);
				WriteStringLength(ref offset, nameLen);
				WriteStringLength(ref offset, valueLen);
				
				string s = pair.Key;
				do
				{
					left = mBuffer.Length - offset;
					if(nameLen < left) left = nameLen;
					
					int written = mEncoding.GetBytes(s, s.Length - nameLen, left, mBuffer, offset); // ASCII only
					nameLen -= written;
					offset += written;
					
					if(offset == mBuffer.Length)
					{
						WriteHeader(RecordType.GetValuesResult, 0, offset - 8);
						WriteOnce(mBuffer, 0, offset);
						offset = 8;
					}
				} while(nameLen > 0);
				
				s = pair.Value;
				do
				{
					left = mBuffer.Length - offset;
					if(valueLen < left) left = valueLen;
					
					int written = mEncoding.GetBytes(s, s.Length - valueLen, left, mBuffer, offset); // ASCII only
					valueLen -= written;
					offset += written;
					
					if(offset == mBuffer.Length)
					{
						WriteHeader(RecordType.GetValuesResult, 0, offset - 8);
						WriteOnce(mBuffer, 0, offset);
						offset = 8;
					}
				} while(valueLen > 0);
			}
		}*/
	}
}
